#include "stdafx.h"
#include "./model.h"

Model::Model()
: m_animationHelper(this)
{

}

Model::~Model()
{
	ModelListIter iter;
	for (iter = m_datalist.begin(); iter != m_datalist.end(); ++iter)
	{
		ModelData& data = *iter;

		delete[] data.vertices;
		data.vertices = NULL;

		delete[] data.normals;
		data.normals = NULL;

		delete[] data.texcoords;
		data.texcoords = NULL;

		delete[] data.indexs;
		data.indexs = NULL;
	}
}

void Model::LoadBy3dsFile(Lib3dsFile* pFile)
{
	Lib3dsMesh* p3dsMesh = NULL;

	for (int i = 0; i < pFile->nmeshes; ++i)
	{
		p3dsMesh = pFile->meshes[i];

		ModelData data;
		LoadMesh(p3dsMesh, data);
		m_datalist.push_back(data);
	}
}

void Model::LoadByModel(Model& model)
{
	ModelListIter iter;
	for (iter = model.m_datalist.begin(); iter != model.m_datalist.end(); ++iter)
	{
		ModelData& srcData = *iter;
		ModelData dstData;

		dstData.strName = srcData.strName;
		dstData.ispanel = srcData.ispanel;

		dstData.nfaces = srcData.nfaces;
		dstData.nvertices = srcData.nvertices;

		dstData.vertices = new vector3d[dstData.nvertices];
		memcpy(dstData.vertices, srcData.vertices, dstData.nvertices * sizeof(vector3d));

		dstData.normals = new vector3d[dstData.nvertices];
		memcpy(dstData.normals, srcData.normals, dstData.nvertices * sizeof(vector3d));

		dstData.texcoords = new vector2d[dstData.nvertices];
		memcpy(dstData.texcoords, srcData.texcoords, dstData.nvertices * sizeof(vector2d));

		dstData.indexs = new short3d[dstData.nfaces];
		memcpy(dstData.indexs, srcData.indexs, dstData.nfaces * sizeof(short3d));

		dstData.material = srcData.material;
		memcpy(dstData.matrix, srcData.matrix, 16 * sizeof(float));

		m_datalist.push_back(dstData);
	}
}

void Model::Render()
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	ModelListIter iter;

	for (iter = m_datalist.begin(); iter != m_datalist.end(); ++iter)
	{
		ModelData data = *iter;

		glVertexPointer(3, GL_FLOAT, 0, data.vertices);
		glNormalPointer(GL_FLOAT, 0, data.normals);

		RenderModel(data);
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
}

void Model::RenderModel(ModelData& data)
{
	glEnable(GL_LIGHTING);

	if (data.material != -1)
	{
		glColor3ub(255, 255, 255);

		glEnable(GL_TEXTURE_2D);

		FireEvent(OnRenderMaterialFunc, OnRenderMaterial, (data.material))
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);

		glTexCoordPointer(2, GL_FLOAT, 0, data.texcoords);

		glPushMatrix();
		glMultMatrixf(reinterpret_cast<GLfloat*>(data.matrix));

		glDrawElements(GL_TRIANGLES, data.nfaces * 3, GL_UNSIGNED_SHORT, data.indexs);

		glPopMatrix();

		glDisableClientState(GL_TEXTURE_COORD_ARRAY);

		glDisable(GL_TEXTURE_2D);
	}
	else
	{
		glPushMatrix();
		glMultMatrixf(reinterpret_cast<GLfloat*>(data.matrix));

		glColor3ub(data.color.r, data.color.g, data.color.b);
		glDrawElements(GL_TRIANGLES, data.nfaces * 3, GL_UNSIGNED_SHORT, data.indexs);

		glPopMatrix();
	}

	glDisable(GL_LIGHTING);
}

//alpha
//		glEnable(GL_BLEND); 
//glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

void Model::LoadMesh(Lib3dsMesh* p3dsMesh, ModelData& data)
{
	lib3ds_matrix_identity(data.matrix);

	data.strName = p3dsMesh->name;
	data.ispanel = false;

	data.nvertices = p3dsMesh->nvertices;
	data.vertices = new vector3d[p3dsMesh->nvertices];
	memcpy(data.vertices, p3dsMesh->vertices, p3dsMesh->nvertices * sizeof(vector3d));

	data.texcoords = new vector2d[p3dsMesh->nvertices];
	memcpy(data.texcoords, p3dsMesh->texcos, p3dsMesh->nvertices * sizeof(vector2d));

	CalcNormalsByVertexs(p3dsMesh, data);
	//lib3ds_mesh_calculate_vertex_normals(p3dsMesh, data.normals);

	data.nfaces = p3dsMesh->nfaces;
	data.indexs = new short3d[p3dsMesh->nfaces * 3];
	
	for (int i = 0; i < p3dsMesh->nfaces; ++i)
	{
		Lib3dsFace* p3dsFace = p3dsMesh->faces + i;
		data.indexs[i][0] = p3dsFace->index[0];
		data.indexs[i][1] = p3dsFace->index[1];
		data.indexs[i][2] = p3dsFace->index[2];

		data.material = p3dsFace->material;		//only one material in a mesh
	}
}

bool Model::GetPanelFace(string strBkgName, float points[3][3])
{
	ModelData* data = GetModelByName(strBkgName);

	if (!data)
	{
		return false;
	}

	data->ispanel = true;
	for (int i = 0; i < 3; ++i)
	{
		for (int y = 0; y < 3; ++y)
		{
			points[i][y] = data->vertices[data->indexs[0][i]][y];
		}
	}

	return true;
}

AnimationHelper* Model::GetAnimationHelper()
{
	return &m_animationHelper;
}

ModelData* Model::GetModelByName(string strName)
{
	ModelData* data = NULL;

	ModelListIter iter;
	for (iter = m_datalist.begin(); iter != m_datalist.end(); ++iter)
	{
		if (iter->strName == strName)
		{
			data = &(*iter);
			break;
		}
	}

	return data;
}

void Model::CalcNormalsByVertexs(Lib3dsMesh* p3dsMesh, ModelData& data)
{
	data.normals = new vector3d[p3dsMesh->nfaces * 3];
	memset(data.normals, 0, p3dsMesh->nfaces * 9);

	vector3d *facenormals = new vector3d[p3dsMesh->nfaces];
	for (int i = 0; i < p3dsMesh->nfaces; ++i)
	{
		Lib3dsFace* p3dsFace = p3dsMesh->faces + i;

		float tmp[3][3];
		for (int y = 0; y < 3; ++y)
		{
			int index = p3dsFace->index[y];
			tmp[y][0] = p3dsMesh->vertices[index][0];
			tmp[y][1] = p3dsMesh->vertices[index][1];
			tmp[y][2] = p3dsMesh->vertices[index][2];
		}

		float out[3];
		CalcNormal(tmp, out);

		memcpy(facenormals + i, out, sizeof(out));
	}

	for (int i = 0; i < p3dsMesh->nfaces; ++i)
	{
		Lib3dsFace* p3dsFace = p3dsMesh->faces + i;

		for (int y = 0; y < 3; ++y)
		{
			int index = p3dsFace->index[y];
			MatrixAdd(data.normals[index], data.normals[index], facenormals[i]);
		}
	}

	for (int i = 0; i < p3dsMesh->nfaces; ++i)
	{
		lib3ds_vector_normalize(data.normals[i]);
	}

	delete[] facenormals;
}

void Model::MatrixAdd(vector3d m, vector3d a, vector3d b) 
{
	for (int i = 0; i < 3; ++i) 
	{
		m[i] = a[i] + b[i];
	}
}

void Model::CalcNormal(float v[3][3], float out[3])
{
	float v1[3],v2[3];
	static const int x = 0;
	static const int y = 1;
	static const int z = 2;

	// Calculate two vectors from the three points
	v1[x] = v[0][x] - v[1][x];
	v1[y] = v[0][y] - v[1][y];
	v1[z] = v[0][z] - v[1][z];

	v2[x] = v[1][x] - v[2][x];
	v2[y] = v[1][y] - v[2][y];
	v2[z] = v[1][z] - v[2][z];

	// Take the cross product of the two vectors to get
	// the normal vector which will be stored in out
	out[x] = v1[y]*v2[z] - v1[z]*v2[y];
	out[y] = v1[z]*v2[x] - v1[x]*v2[z];
	out[z] = v1[x]*v2[y] - v1[y]*v2[x];

	// Normalize the vector (shorten length to one)
	ReduceToUnit(out);
}

void Model::ReduceToUnit(float vector[3])
{
	float length;

	// Calculate the length of the vector		
	length = (float)sqrt((vector[0]*vector[0]) + 
		(vector[1]*vector[1]) +
		(vector[2]*vector[2]));

	// Keep the program from blowing up by providing an exceptable
	// value for vectors that may calculated too close to zero.
	if(length == 0.0f)
		length = 1.0f;

	// Dividing each element by the length will result in a
	// unit normal vector.
	vector[0] /= length;
	vector[1] /= length;
	vector[2] /= length;
}